/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package org.latestbit.smartobjects.network.tcp;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.latestbit.smartobjects.network.IAcceptor;
import org.latestbit.smartobjects.network.IAcceptorListener;
import org.latestbit.smartobjects.utils.PostActionListenersContainer;

public class TCPAcceptor implements IAcceptor, ISocketMultiplexerListener {
	private URI endpointURI;
    private ServerSocketChannel serverChannel = null;
    private SelectionKey serverChannelKey = null;
    
    private Set<TCPAcceptorConnection> acceptorConnections 
    	= new HashSet<TCPAcceptorConnection>();
    
    protected List<IAcceptorListener> listeners = new LinkedList<IAcceptorListener>();
    protected PostActionListenersContainer<IAcceptorListener> palAcceptorListeners = new PostActionListenersContainer<IAcceptorListener> ();
    
    private ReadWriteLock listenersLock =  new ReentrantReadWriteLock();
	private TCPSharedResources resources;
	private SocketMultiplexer multiplexer;
	private final String ACCEPTOR_MULTIPLEXER_GROUP_NAME = "ACCEPTORS";

    
    
    public TCPAcceptor(TCPSharedResources resources) throws IOException {
    	this.resources = resources;
    	this.resources.addRef();
    	this.multiplexer = resources.getSocketMultiplexer(ACCEPTOR_MULTIPLEXER_GROUP_NAME);
    }
    
    public void finalize() {
    	destroy();
    }
    
    public void destroy() {
    	stop();
    	this.resources.release();    	
    }
	
	@Override
	public void addListener(IAcceptorListener listener) {
		listenersLock.writeLock().lock();
		try {
			if(!palAcceptorListeners.isActivated()) {
				listeners.add(listener);
			}
			else
				palAcceptorListeners.putListenerToAdd(listener);
		}
		finally {
			listenersLock.writeLock().unlock();
		}
	}

	@Override
	public void delListener(IAcceptorListener listener) {
		listenersLock.writeLock().lock();
		try {
			if(!palAcceptorListeners.isActivated()) {
				listeners.remove(listener);
			}
			else
				palAcceptorListeners.putListenerToRemove(listener);			
		}
		finally {
			listenersLock.writeLock().unlock();
		}		
	}

	@Override
	public URI getEndpointURI() {
		return endpointURI;
	}
	
	@Override
	public void setEndpointURI(URI listenerURI) {
		this.endpointURI = listenerURI;
	}	

	@Override
	public synchronized void start() throws IOException {
		if(isOpen())
			stop();
        serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().setReuseAddress(true);
        serverChannel.socket().bind(
        	new InetSocketAddress( endpointURI.getHost(), endpointURI.getPort())
        );
        serverChannelKey = multiplexer.addChannel(serverChannel, SelectionKey.OP_ACCEPT, this);
	}

	@Override
	public synchronized void stop() {
        if(isOpen()) {
            try {
            	if(serverChannelKey!=null && serverChannelKey.isValid())
            		multiplexer.removeChannel(serverChannelKey);
            	serverChannel.close();
            }
            catch (IOException e) {}            
        }
        
        Set<TCPAcceptorConnection> acceptorConnectionsCopy;
		synchronized(acceptorConnections) {
			acceptorConnectionsCopy= new HashSet<TCPAcceptorConnection>();
        	acceptorConnectionsCopy.addAll ( acceptorConnections );
        	acceptorConnections.clear();
        }
        
		
    	for(TCPAcceptorConnection connection: acceptorConnectionsCopy) {
    		connection.close();
    	}
	}
	
	@Override
	public synchronized boolean isOpen() {
		return serverChannel != null && serverChannel.isOpen();
	}

	@Override
	public synchronized void onSelectedEvent(SelectionKey selectionKey) {
		if(!isOpen())
			return;
		try {
			SocketChannel clientChannel = this.serverChannel.accept();
			URI clientURI = new URI(
					endpointURI.getScheme()+"://"+clientChannel.socket().getInetAddress().getHostAddress()+":"+clientChannel.socket().getPort()
			);
			
			TCPAcceptorConnection connection = new TCPAcceptorConnection(
					resources,
					this
			);
			connection.setChannel(clientChannel);
			connection.setEndpointURI(clientURI);
			
			synchronized(acceptorConnections) {
				acceptorConnections.add(connection);
			}
						
			if(connection.getHandshakeManager() ==null) {
				// If there is no handshake procedure
				fireOnAccepted(connection);
			}
			
			
			connection.start();
		}
		catch(IOException ex) {} 
		catch (URISyntaxException ex) {
			ex.printStackTrace();
		};
	}
	
	public void onCheckKeepAlive(SelectionKey key) {
		// Nothing to do
	}

	protected void fireOnAccepted(TCPAcceptorConnection connection) {
		synchronized(listeners) {
			palAcceptorListeners.activate();
			try {
				for(IAcceptorListener listener : listeners) {
					listener.onAccepted(connection);
				}
			}
			finally {
				processingPostActionAcceptorListeners();
			}
		}
	}
	
	protected void processingPostActionAcceptorListeners() {
		palAcceptorListeners.deactivate();
		try {
			for ( PostActionListenersContainer<IAcceptorListener>.PostActionQueueItem item : palAcceptorListeners.getPostActionQueue() ) {
				if( item.isActionAdd() ) {
					addListener ( item.getListener() );
				}
				else
				if( item.isActionRemove() ) {
					delListener ( item.getListener() );
				}
			}
		}
		finally {
			palAcceptorListeners.reset();
		}	
	}

	public void removeConnection(TCPAcceptorConnection acceptorConnection) {
		synchronized(acceptorConnections) {
			acceptorConnections.remove(acceptorConnection);
		}
	}

}
